@file:Suppress("SpellCheckingInspection")

package han.cirno.corrupt.components.school.impl.fafu

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import han.cirno.corrupt.Application
import han.cirno.corrupt.R
import han.cirno.corrupt.components.cnn.LarvaWrapper
import han.cirno.corrupt.components.school.SchoolImpl
import han.cirno.corrupt.components.school.SchoolProvider
import han.cirno.corrupt.components.school.impl.CommonNetClient
import han.cirno.corrupt.data.instances.UserSchoolData
import han.cirno.corrupt.throwables.LoginExceptions
import han.cirno.corrupt.util.RegexUtil
import han.cirno.corrupt.util.getBodyStringAndClose
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response

/**
 * 好丑。。。
 * 现在好点了吧。。。？
 * @author CCirno
 */
class FafuEduClient: CommonNetClient<FafuEduConst.Payload>(
    {Request.Builder().url(FafuEduConst.getMainPageAddress()).build()},
    { FafuEduConst.isLoopBacked(it) }
) {
    companion object {
        val instance: FafuEduClient =
            FafuEduClient()
    }

    override fun tryLogin(tryLoginResponse: Response) {
        makeFirstConnectionAndLogin()
        tryToLoginMainPage()
    }

    private fun makeFirstConnectionAndLogin(){
        makeFirstConnection()
        val alert = login()
        if (alert != null)
            when {
                alert.contains("密码错误，您还有") -> {
                    val times =
                        RegexUtil.matchFirst("(?<=您还有).*?(?=次尝试机会)", alert)
                    throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_login_invalidData_password, null, times?:"<null>")
                }
                alert == "用户名不存在或未按照要求参加教学活动！！" -> throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_login_invalidData_username,null)
                alert == "验证码不正确！！" -> throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_login_invalidData_checkCode,null)
                alert == "用户名不能为空！！"->throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_login_invalidData_emptyUsername,null)
                alert == "密码不能为空！！"->throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_login_invalidData_emptyPassword,null)
                else-> throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_login_invalidData_other,null,alert)
            }
    }

    private fun tryToLoginMainPage(){
        val provider =getProvider()
        val (body,success) = simpleGetRequest { Request.Builder().url(FafuEduConst.getMainPageAddress()).build() }
            .use { Pair(it.body?.string(),!FafuEduConst.isLoopBacked(
                it.request.url
            )
            ) }
        if (success){
            var name:String?
            if(RegexUtil.matchFirst(
                    "(?<=\"xhxm\\\">).*?(?=同学)",
                    body?: throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_name_fetch_failed,R.string.login_error_sub_null_body))
                .also { name=it }==null)
                throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_name_fetch_failed,R.string.login_error_sub_regex_not_match)
            provider.dataOperation()
                .write(SchoolProvider.KEY_USERNAME,name!!)
                .save()

            return
        }
        throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_login_failed,R.string.login_error_sub_loopback)
    }

    private fun simpleGetRequest(supplier: ()->Request):Response{
        val request = supplier()
        val call = okHttpClient.newCall(request)
        return call.execute()
    }

    private fun makeFirstConnection() {
        val provider =getProvider()
        val (body,backUrl) = simpleGetRequest { Request.Builder().url(FafuEduConst.ROOT_ADDRESS).build() }.use {
            val body = (it.body
                ?: throw LoginExceptions.NetworkExceptionWithResId(
                    R.string.login_error_viewState_fetch_failed,R.string.login_error_sub_null_body)).string()
            val backUrl = it.request.url.toString()
            Pair(body,backUrl)
        }
        val token = RegexUtil.matchFirst("\\(\\w+\\)", backUrl)
            ?: throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_token_fetch_failed,R.string.login_error_sub_regex_not_match)
        val viewState=findViewState(body)
        provider.dataOperation()
            .write(SchoolProvider.KEY_TOKEN, token)
            .write(FafuSchool.KEY_VIEW_STATE,viewState!!)
            .save()
    }

    private fun login():String?{
        val provider =getProvider()
        var token:String?=null
        var viewState:String?=null
        var serial:String?=null
        var password:String?=null
        provider.dataOperation()
            .get(FafuSchool.KEY_VIEW_STATE,String::class){viewState=it}
            .get(SchoolProvider.KEY_TOKEN,String::class) { token = it }
            .get(SchoolProvider.KEY_ID,String::class){serial=it}
            .get(SchoolProvider.KEY_PASSWORD,String::class){password=it}
        val checkCode=predictCheckCodeValue()
        val postForm = Request.Builder().url("http://jwgl.fafu.edu.cn/$token/default2.aspx")
            .post(
                FormBody.Builder()
                    .add("__VIEWSTATE",
                        viewState?:throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_viewState_fetch_failed,R.string.login_error_sub_null_body))
                    .add("txtUserName", serial?:throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_invalid_data, null))
                    .add("TextBox2", password?:throw LoginExceptions.InvalidDataExceptionWithResId(R.string.login_error_invalid_data,null))
                    .add("txtSecretCode", checkCode)
                    .add("RadioButtonList1", "学生")
                    .add("Button1", "")
                    .add("lbLanguage", "")
                    .add("hidPdrs", "")
                    .add("hidsc", "").build()
            ).build()
        val call = okHttpClient.newCall(postForm)
        val response = call.execute().getBodyStringAndClose()?: throw LoginExceptions.NetworkExceptionWithResId(R.string.login_error_login_failed,R.string.login_error_sub_null_body)
        val alert = RegexUtil.matchFirst("(?<=(alert\\(')).+?(?='\\))", response)
        return alert
    }

    private fun predictCheckCodeValue():String{
        val larvaWrapper:LarvaWrapper?
        Application.getApplicationContext().assets.open("net.larva").use { larvaWrapper=LarvaWrapper.fromStream(it) }
        if (larvaWrapper==null)throw LoginExceptions.FatalExceptionWithResId(R.string.login_error_checkCode_model_fault,null)
        return larvaWrapper.predict(getCheckCodeBitmap())
    }

    private fun getCheckCodeBitmap():Bitmap{
        var token:String?=null
        getProvider().dataOperation().get(SchoolProvider.KEY_TOKEN,String::class){token=it}
        return simpleGetRequest { Request.Builder().url("http://jwgl.fafu.edu.cn/$token/CheckCode.aspx").build() }.use {
            BitmapFactory.decodeStream(
                (it.body ?: throw LoginExceptions.NetworkExceptionWithResId(
                    R.string.login_error_checkCode_fetch_failed,
                    R.string.login_error_sub_null_body
                )).byteStream()
            )
        }
    }

    private fun getProvider(): SchoolProvider {
        return SchoolImpl.getImpl(UserSchoolData.School.FAFU)!!
    }

    fun findViewState(body:String):String?=
        RegexUtil.matchFirst("(?<=\"__VIEWSTATE\" value=\").*?(?=\" />)",body)
}